import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;


import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;

public class OfficeServicesClient implements OfficeMsgs{

	/**
	 * The service type to browse for. This should be the same as the one you use
	 * to register services with.
	 */
	public static final String SERVICE_TYPE = "GroupD._udp.local.";
	
	private static final String FORMAT_ERROR = "Formatting error in reply from server. Please try again...";
	private static final String INCORRECT_MSG = "Incorrect message received from server. Please try again...";
	private static final String INVALID_CMD_LINE_OPT = "\nWarning! You must choose from the available options only.";
	
	private String host;
	private int port;

	/**
	 * This class implements the ServiceListener interface which receives
	 * notifications when services are discovered and lost. Note that in your
	 * implementations the listener will probably form part of another class; not
	 * a static, stand-alone class like this. Think about how you might add
	 * listeners in the Swing framework for example.
	 */
	class SampleListener implements ServiceListener {
		public void serviceAdded(final ServiceEvent event) {
			System.out.println("Service added   : " + event.getName() + "."
					+ event.getType());
			// The following line is required to get all information associated
			// with a service registration - not just the name and type - for
			// example, the port number and properties. Notification is sent to the
			// serviceResolved(...) method which the request has been completed.
			event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0);
		}

		public void serviceRemoved(ServiceEvent event) {
			System.out.println("Service removed : " + event.getName() + "."
					+ event.getType());
			host = null;
		}

		public void serviceResolved(ServiceEvent event) {
			System.out.println("Service resolved: " + event.getInfo().getName()
					+ ", host: " + event.getInfo().getHostAddress() + ", port: "
					+ event.getInfo().getPort());

			setHost(event.getInfo().getHostAddress());
			setPort(event.getInfo().getPort());
		}
	}

	public OfficeServicesClient(JmDNS jmDns) {

		// Work the magic: this is where the service listener is registered.
		jmDns.addServiceListener(SERVICE_TYPE, new SampleListener());
	}

	synchronized private String getHost() {
		return host;
	}

	synchronized private int getPort() {
		return port;
	}

	synchronized private void setHost(String host) {
		this.host = host;
	}

	synchronized private void setPort(int port) {
		this.port = port;
	}
/*
	public ArrayList<Office> getOfficeList() {
		 ArrayList<Office> officeList = new ArrayList<Office>();
		 sendMessage(OFFICE_GET_LIST);
	}
*/
	
	/**
	 * Creates a connection to the server and sends a message.
	 * 
	 * @param a_message
	 *          the message to send to the server.
	 */
	public String sendMessage(String a_message) {
		try {
			
			String reply = null;
			
			// Create a connection to the server.
			Socket toServer = new Socket(host, port);

			PrintWriter out = new PrintWriter(toServer.getOutputStream(), true);

			// Write the message to the socket.
			out.println(a_message);

			BufferedReader in = new BufferedReader(new InputStreamReader(toServer.getInputStream()));
			reply = in.readLine();
			System.out.println("The reply was : " + reply);

			// tidy up
			in.close();
			out.close();
			toServer.close();
			return reply;
			
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} catch (SecurityException se) {
			se.printStackTrace();
		}
		return null;
	}

	private void processListReply(String reply){
		int currDelim = 0;
		int numOutlets = 0;
		String leftToParse = reply;
		String field = null;
		
		try {
		
			// Check the msg type
			if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM)) == -1){
				System.out.println(FORMAT_ERROR);
				return;
			}
			
			field = leftToParse.substring(0, currDelim);
			
			if (field.compareTo(OFFICE_LIST_MSG) != 0){
				System.out.println(INCORRECT_MSG);
				return;
			}
			
			// Get the number of outlets
			leftToParse = leftToParse.substring(currDelim);
			
			if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM,1)) == -1){
				System.out.println(FORMAT_ERROR);
				return;
			}
			field = leftToParse.substring(1, currDelim);
			numOutlets = Integer.valueOf(field);
			
			if (numOutlets <= 0){
				System.out.println("Sorry, there are no outlets offering Office Services.");
				return;
			}
			
			// Parse each set of Office info and create Office objects.
			int ref =0;
			String name = null;
			String loc = null;
			String phone = null;
			String email = null;
			
			for (int i=0; i < numOutlets; i++){
				for (int j=0; j < Office.NUM_MANDATORY_FIELDS; j++){

					leftToParse = leftToParse.substring(currDelim);

					if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM,1)) == -1){
						System.out.println(FORMAT_ERROR);
						return;
					}
					
					field = leftToParse.substring(1, currDelim);
									
					// parse Office fields
					switch (j){
					case 0: // ref
						ref = Integer.valueOf(field.trim());
						break;
					case 1: // name
						name = field.trim();
						break;
					case 2: // location
						loc = field.trim();
						break;
					case 3: // phone
						phone = field.trim();
						break;
					case 4: // email
						email = field.trim();
						break;
					}
				}
				
				new Office(ref, name, loc, phone, email).printMainInfoToConsole();
			}
		
		}catch (NumberFormatException nfe){
			System.out.println(FORMAT_ERROR);
		}
	}
	
	
	private void processInfoReply(String reply) {
		int currDelim = 0;
		String leftToParse = reply;
		String field = null;
		String offRef = null;
		int numServices = 0;

		try {
			
			// Check the msg type
			if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM)) == -1){
				System.out.println(FORMAT_ERROR);
				return;
			}
			
			field = leftToParse.substring(0, currDelim);
			
			if (field.compareTo(OFFICE_INFO_MSG) != 0){
				System.out.println(INCORRECT_MSG);
				return;
			}

			// Get the office ref
			leftToParse = leftToParse.substring(currDelim);
			
			if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM,1)) == -1){
				System.out.println(FORMAT_ERROR);
				return;
			}
			field = leftToParse.substring(1, currDelim);
			offRef = field.trim();
			
			// Get the number of services
			leftToParse = leftToParse.substring(currDelim);
			
			if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM,1)) == -1){
				System.out.println(FORMAT_ERROR);
				return;
			}
			field = leftToParse.substring(1, currDelim);
			numServices = Integer.valueOf(field);
			
			if (numServices <= 0){
				System.out.println("Sorry, there are no Office Services listed for this outlet.");
				return;
			}
			
			System.out.println("Office services (for Office ref: " + offRef + " )");
			System.out.println();
			for (int i=0; i < numServices; i++){
				leftToParse = leftToParse.substring(currDelim);

				if ((currDelim = leftToParse.indexOf(OFFICE_MSG_DELIM,1)) == -1){
					System.out.println(FORMAT_ERROR);
					return;
				}
				
				field = leftToParse.substring(1, currDelim);
				
				// if we were storing office objs from call to getList, we could add services to the 
				// office objects but for now, it's sufficient to just output them to the screen.
				System.out.println("\t" + field.trim());
			}
			
		}catch (NumberFormatException nfe){
			System.out.println(FORMAT_ERROR);
		}
	}
	
	public static void main(String[] args) throws IOException {

		/*
		// Check we have the right number of arguments and parse
		String localhost = null;
		if (args.length == 1) {
			localhost = args[0];
			System.out.println("Host Interface supplied: " + localhost);
		}
		*/
		
		/*
		// Again, you can specify which network interface you would like to browse
		// for services on; see commented line.
		JmDNS jmdns = null;
		if (localhost == null)
			jmdns = JmDNS.create();
		else
			jmdns = JmDNS.create(InetAddress.getLocalHost());

		System.out.println("InetAddress is : " + InetAddress.getLocalHost());
		 */
		
		final JmDNS jmdns = JmDNS.create();
		
		OfficeServicesClient client = new OfficeServicesClient(jmdns);
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		String cmdln_in = null;
		
		// main loop catering for command line i/o
		MAIN_LOOP:
		while (true) {
			System.out.println();
			System.out.println("Choose one of the following options:");
			System.out.println();
			System.out.println("  1              - list of outlets offering Office Services in the airport.");
			System.out.println("  2 <office ref> - details of a specific outlet offering Office Services.");
			System.out.println("  3 <office ref> - map showing the location of the outlet.");
			System.out.println("  4              - quit!");
			System.out.println();
			System.out.println("Please select one of the options, along with an office ref if needed, and press Enter.");
			System.out.println();
			
			cmdln_in = in.readLine();
			cmdln_in = cmdln_in.trim();
			try {
				int option = Integer.valueOf(cmdln_in.substring(0,1));
				String reply = null;
				
				switch (option){
				case 1:
					reply = client.sendMessage(OFFICE_GET_LIST_MSG + OFFICE_MSG_DELIM);
					if (reply != null)
						client.processListReply(reply);
					break;
					
				case 2:
					String ref = cmdln_in.substring(1).trim();
					String msg = OFFICE_GET_INFO_MSG + OFFICE_MSG_DELIM + ref + OFFICE_MSG_DELIM;
					reply = client.sendMessage(msg);
					if (reply != null)
						client.processInfoReply(reply);
					break;
					
				case 3:
					
					break;
					
				case 4:
					System.out.println("Exiting the application");
					break MAIN_LOOP;

				default:
					System.out.println(INVALID_CMD_LINE_OPT);
					continue;
				}
			}
			catch (NumberFormatException nfe){
				System.out.println(INVALID_CMD_LINE_OPT);				
			}
		}

		in.close();
		jmdns.close();
		System.exit(0);
	}
}